using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.Text.RegularExpressions;

using System.Linq;

namespace Neznayka
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a clustered key column in a trailing position in another index.
    /// This rule only applies to Indexes
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        NeznaykaConstants.NameSpace,
        NeznaykaConstants.CheckClusteredKeyColumnsNotIncludedInIndexRuleId,
        NeznaykaConstants.ResourceBaseName,
        NeznaykaConstants.CheckClusteredKeyColumnsNotIncludedInIndex_RuleName,
        NeznaykaConstants.CategoryDatabaseStructures,
        DescriptionResourceId = NeznaykaConstants.CheckClusteredKeyColumnsNotIncludedInIndex_ProblemDescription)]
    //[SupportedElementType(typeof(ISqlProcedure))]
    //[SupportedElementType(typeof(ISqlTrigger))]
    //[SupportedElementType(typeof(ISqlFunction))]
    //[SupportedElementType(typeof(ISqlView))]
    //[SupportedElementType(typeof(ISqlTable))]
    [SupportedElementType(typeof(ISqlIndex))]
    //[SupportedElementType(typeof(ISql90Index))]
    //[SupportedElementType(typeof(ISql100Index))]
    //    [SupportedElementType(typeof(ISqlUniqueConstraint))]
    public class CheckClusteredKeyColumnsNotIncludedInIndexRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "x")]
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {
            // (Re)-Load Environment settings
            List<DataRuleProblem> problems;
            SqlSchemaModel sqlSchemaModel;
            ISqlModelElement sqlElement;
            TSqlFragment sqlFragment;
            DMVRuleSetup.RuleSetup(context, out problems, out sqlSchemaModel, out sqlElement, out sqlFragment);

            // Refresh cached index/constraints/tables lists from Model
            DMVRuleSetup.RefreshDDLCache(sqlSchemaModel);

            //// Get Database Schema and name of this model element.
            //string selfSchema           = sqlElement.Name.Parts[0];
            //string selfTable            = sqlElement.Name.Parts[1];

            string owningObjectSchema   = null;
            string owningObjectTable    = null;

            List<TSqlFragment> issues   = new List<TSqlFragment>();

            // do unique constraints participate in this rule/law ? -- NO
            // Neither do unique indexes
            ISqlIndex thisIX            = sqlElement as ISqlIndex;
            // if we're a unique index, then this check doesn't apply - we need all the columns in the index to be in the index.!
            // if we're a clustered index then this rule definitely doesn't apply !!!!!!
            if (thisIX != null && !thisIX.IsUnique && !thisIX.IsClustered)
            {
                owningObjectSchema          = thisIX.IndexedObject.Name.Parts[0];
                owningObjectTable           = thisIX.IndexedObject.Name.Parts[1];
                List<String> ClusterColumns = ModelIndexAndKeysUtils.getClusteredKeyColumns(owningObjectSchema, owningObjectTable);


                // We need to create a problem where the leading n (n>0) columns in the index are not in the clustered columns
                // And the remainder all are.
                // It's questionable as to whether indexes that then have trailing elements not in the cluster should be in our list
                // of problems.

                Boolean leadingEdgeNotInCluster         = true;
                Boolean trailingEdgeInCluster           = false;
                Boolean checkingLeadingEdge             = true;
                Boolean checkingTrailingEdge            = false;
                Boolean first                           = true;
                //List<String> LeadingEdgeIndexColumns = new List<String>();
                foreach (var c in thisIX.ColumnSpecifications) // in the right order we hope
                {
                    // get column name - looks to be an on-demand structure so we need this rigmarole to access it.
                    String ixColumnName = "";
                    foreach (var n in c.Column.Name.Parts)
                    {
                        ixColumnName = n;
                    }

                    if (checkingLeadingEdge)
                    {
                        if (first)
                        {
                            if (ClusterColumns.Contains(ixColumnName))
                            {
                                leadingEdgeNotInCluster = false;
                                checkingTrailingEdge    = true;
                            }
                            first = false;
                        }
                        else
                        {
                            if (ClusterColumns.Contains(ixColumnName))
                            {
                                checkingLeadingEdge     = false;
                                checkingTrailingEdge    = true;
                            }
                        }
                    }
                    if (checkingTrailingEdge)
                    {
                        if (ClusterColumns.Contains(ixColumnName))
                        {
                            trailingEdgeInCluster = true;
                        }
                    }
                }


                if (leadingEdgeNotInCluster && trailingEdgeInCluster )
                {
                    issues.Add(sqlFragment);
                }
                else
                {
                    // if it's a 2005+ index, also check the included columns
                    ISql90Index this90IX                    = sqlElement as ISql90Index;
                    if (this90IX != null)
                    {
                        List<String> IncludedIndexColumns   = new List<String>();
                        foreach (var c in this90IX.IncludedColumns)
                        {

                            String ixColumnName = "";
                            foreach (var n in c.Name.Parts)
                            {
                                ixColumnName = n;
                            }
                            IncludedIndexColumns.Add(ixColumnName);
                        }
                        List<String> CommonList = IncludedIndexColumns.Intersect(ClusterColumns, SqlComparer.Comparer).ToList();
                        //lazy collection - needs iterating to find if it has anything
                        bool clusteredKeyColumnFoundInIncludedColumns = false;
                        foreach (var x in CommonList)
                        {
                            clusteredKeyColumnFoundInIncludedColumns = true;
                            break;
                        }


                        if (clusteredKeyColumnFoundInIncludedColumns)
                        {
                            issues.Add(sqlFragment);
                        }
                    }
                }


                // Create problems for each object
                foreach (TSqlFragment issue in issues)
                {
                    DataRuleProblem problem = new DataRuleProblem(this,
                                                String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                                sqlElement);

                    SqlRuleUtils.UpdateProblemPosition(problem, issue.StartOffset, issue.FragmentLength);
                    problems.Add(problem);
                }
            }
            return problems;
        }

        #endregion

    }
}
